package com.ww.springboot.sharding.config;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DateUtil;
import com.google.common.collect.Range;
import org.apache.shardingsphere.api.sharding.complex.ComplexKeysShardingAlgorithm;
import org.apache.shardingsphere.api.sharding.complex.ComplexKeysShardingValue;

import java.util.*;

/**
 * 物流轨迹 分表 按月份分表
 *
 * @author liw
 * @date 2020/9/3 15:19
 */
public class OrderTraceComplexAlgorithm implements ComplexKeysShardingAlgorithm<Date> {

    /**
     * 出库时间列
     */
    private final String COLUMN_NAME_OUT_STOCK = "o_outstock_time";

    /**
     * 付款时间列
     */
    private final String COLUMN_NAME_PAID_TIME = "o_paid_time";

    /**
     * 根据相应的策略返回对应的table名
     *
     * @param collection
     * @param complexKeysShardingValue
     * @return
     */
    @Override
    public Collection<String> doSharding(Collection<String> collection,
                                         ComplexKeysShardingValue<Date> complexKeysShardingValue) {
        // 精确时间 匹配策略 用于=查询
        Map<String, Collection<Date>> columnNameAndShardingValuesMap = complexKeysShardingValue.getColumnNameAndShardingValuesMap();
        // 范围时间 匹配策略 用于between in等查询
        Map<String, Range<Date>> columnNameAndRangeValuesMap = complexKeysShardingValue.getColumnNameAndRangeValuesMap();
        if (CollUtil.isNotEmpty(columnNameAndShardingValuesMap)) {
            return complexPreciseStrategy(collection, columnNameAndShardingValuesMap);
        }
        if (CollUtil.isNotEmpty(columnNameAndRangeValuesMap)) {
            return complexRangeStrategy(collection, columnNameAndShardingValuesMap, columnNameAndRangeValuesMap);
        }
        return null;
    }


    /**
     * 用于时间范围的复合分片策略
     *
     * @param collection
     * @param columnNameAndShardingValuesMap
     * @param columnNameAndRangeValuesMap
     * @return
     */
    private Collection<String> complexRangeStrategy(Collection<String> collection, Map<String, Collection<Date>> columnNameAndShardingValuesMap, Map<String, Range<Date>> columnNameAndRangeValuesMap) {
        List<String> tableNames = new ArrayList<>();
        // 如果包含出库时间列 根据出库时间列按月份路由 查询出库时间当月的表
        if (columnNameAndRangeValuesMap.containsKey(COLUMN_NAME_OUT_STOCK)) {
            Range<Date> dateRange = columnNameAndRangeValuesMap.get(COLUMN_NAME_OUT_STOCK);
            Date minDate = dateRange.lowerEndpoint();
            Date maxDate = dateRange.upperEndpoint();
            // 起始年份
            compareWithOutStockTime(collection, tableNames, minDate, maxDate);
            return tableNames;
        }
        if (columnNameAndRangeValuesMap.containsKey(COLUMN_NAME_PAID_TIME)) {
            Range<Date> dateRange = columnNameAndRangeValuesMap.get(COLUMN_NAME_PAID_TIME);
            Date minDate = dateRange.lowerEndpoint();
            Date maxDate = dateRange.upperEndpoint();
            compareWithPaidTime(collection, tableNames, minDate, maxDate);
            return tableNames;
        }
        return null;
    }


    /**
     * 精确时间匹配策略
     *
     * @param collection
     * @param columnNameAndShardingValuesMap
     * @return
     */
    private Collection<String> complexPreciseStrategy(Collection<String> collection, Map<String, Collection<Date>> columnNameAndShardingValuesMap) {
        List<String> tableNames = new ArrayList<>();
        // 如果包含出库时间列 根据出库时间列按月份路由 查询出库时间当月的表
        if (columnNameAndShardingValuesMap.containsKey(COLUMN_NAME_OUT_STOCK)) {
            Collection<Date> dates = columnNameAndShardingValuesMap.get(COLUMN_NAME_OUT_STOCK);
            List<Date> sort = CollUtil.sort(dates, Comparator.comparing(Date::getTime));
            Date minDate = sort.get(0);
            Date maxDate = sort.get(sort.size() - 1);
            compareWithOutStockTime(collection, tableNames, minDate, maxDate);
            return tableNames;
        }
        // 包含付款时间列的时候 由于付款时间之后可能出现很长时间未出库的情况 默认查询付款时间之后3个月的表
        if (columnNameAndShardingValuesMap.containsKey(COLUMN_NAME_PAID_TIME)) {
            Collection<Date> dates = columnNameAndShardingValuesMap.get(COLUMN_NAME_PAID_TIME);
            List<Date> sort = CollUtil.sort(dates, Comparator.comparing(Date::getTime));
            Date minDate = sort.get(0);
            Date maxDate = sort.get(sort.size() - 1);
            // 起始年份
            compareWithPaidTime(collection, tableNames, minDate, maxDate);
            return tableNames;
        }
        return null;
    }


    /**
     * 比较出库时间列
     *
     * @param collection
     * @param tableNames
     * @param minDate
     * @param maxDate
     */
    private void compareWithOutStockTime(Collection<String> collection, List<String> tableNames, Date minDate, Date maxDate) {
        // 起始年份
        int minYear = DateUtil.year(minDate);
        // 结束年份
        int maxYear = DateUtil.year(maxDate);
        // 起始月份
        int minMonth = DateUtil.month(minDate) + 1;
        // 结束月份
        int maxMonth = DateUtil.month(maxDate) + 1;
        for (String tableName : collection) {
            // 表名根据下划线拆分
            String[] s = tableName.split("_");
            // 倒数第二个是年份
            int year = Integer.parseInt(s[s.length - 2]);
            // 倒数第一个是月份
            int month = Integer.parseInt((s[s.length - 1]));
            // 判断是否在范围内
            boolean flag = minYear <= year && maxYear >= year && minMonth <= month && maxMonth >= month;
            if (flag) {
                tableNames.add(tableName);
            }
        }
    }


    /**
     * 比较付款时间列
     *
     * @param collection
     * @param tableNames
     * @param minDate
     * @param maxDate
     */
    private void compareWithPaidTime(Collection<String> collection,
                                     List<String> tableNames,
                                     Date minDate, Date maxDate) {
        // 起始年份
        int minYear = DateUtil.year(minDate);
        // 结束年份
        int maxYear = DateUtil.year(maxDate);
        // 起始月份
        int minMonth = DateUtil.month(minDate) + 1;
        // 结束月份
        int maxMonth = DateUtil.month(maxDate) + 1 + 3;
        for (String tableName : collection) {
            // 表名根据下划线拆分
            String[] s = tableName.split("_");
            // 倒数第二个是年份
            int year = Integer.parseInt(s[s.length - 2]);
            // 倒数第一个是月份
            int month = Integer.parseInt((s[s.length - 1]));
            // 判断是否在范围内
            boolean flag = minYear <= year && maxYear >= year && minMonth <= month && maxMonth >= month;
            if (flag) {
                tableNames.add(tableName);
            }
        }
    }
}
